/**
 * @fileOverview Cube Object
 * @author: simonsliu
 * @date: 11-11-17 - 下午5:16
 */
(function() {
	var CubeObject = Cube.Object = {

		/**
		 * Converts a `name` - `value` pair to an array of objects with support for nested structures. Useful to construct
		 * query strings. For example:
		 *
		 *     var objects = Cube.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
		 *
		 *     // objects then equals:
		 *     [
		 *         { name: 'hobbies', value: 'reading' },
		 *         { name: 'hobbies', value: 'cooking' },
		 *         { name: 'hobbies', value: 'swimming' },
		 *     ];
		 *
		 *     var objects = Cube.Object.toQueryObjects('dateOfBirth', {
		 *         day: 3,
		 *         month: 8,
		 *         year: 1987,
		 *         extra: {
		 *             hour: 4
		 *             minute: 30
		 *         }
		 *     }, true); // Recursive
		 *
		 *     // objects then equals:
		 *     [
		 *         { name: 'dateOfBirth[day]', value: 3 },
		 *         { name: 'dateOfBirth[month]', value: 8 },
		 *         { name: 'dateOfBirth[year]', value: 1987 },
		 *         { name: 'dateOfBirth[extra][hour]', value: 4 },
		 *         { name: 'dateOfBirth[extra][minute]', value: 30 },
		 *     ];
		 *
		 * @param {String} name
		 * @param {Object/Array} value
		 * @param {Boolean} [recursive=false] True to traverse object recursively
		 * @return {Array}
		 */
		toQueryObjects: function(name, value, recursive) {
			var self = CubeObject.toQueryObjects,
				objects = [],
				i, ln;

			if (Cube.isArray(value)) {
				for (i = 0, ln = value.length; i < ln; i++) {
					if (recursive) {
						objects = objects.concat(self(name + '[' + i + ']', value[i], true));
					}
					else {
						objects.push({
							name: name,
							value: value[i]
						});
					}
				}
			}
			else if (Cube.isObject(value)) {
				for (i in value) {
					if (value.hasOwnProperty(i)) {
						if (recursive) {
							objects = objects.concat(self(name + '[' + i + ']', value[i], true));
						}
						else {
							objects.push({
								name: name,
								value: value[i]
							});
						}
					}
				}
			}
			else {
				objects.push({
					name: name,
					value: value
				});
			}

			return objects;
		},

		/**
		 * Takes an object and converts it to an encoded query string.
		 *
		 * Non-recursive:
		 *
		 *     Cube.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
		 *     Cube.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
		 *     Cube.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
		 *     Cube.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
		 *     Cube.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
		 *
		 * Recursive:
		 *
		 *     Cube.Object.toQueryString({
		 *         username: 'Jacky',
		 *         dateOfBirth: {
		 *             day: 1,
		 *             month: 2,
		 *             year: 1911
		 *         },
		 *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
		 *     }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
		 *     // username=Jacky
		 *     //    &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
		 *     //    &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
		 *
		 * @param {Object} object The object to encode
		 * @param {Boolean} [recursive=false] Whether or not to interpret the object in recursive format.
		 * (PHP / Ruby on Rails servers and similar).
		 * @return {String} queryString
		 */
		toQueryString: function(object, recursive) {
			var paramObjects = [],
				params = [],
				i, j, ln, paramObject, value;

			for (i in object) {
				if (object.hasOwnProperty(i)) {
					paramObjects = paramObjects.concat(CubeObject.toQueryObjects(i, object[i], recursive));
				}
			}

			for (j = 0, ln = paramObjects.length; j < ln; j++) {
				paramObject = paramObjects[j];
				value = paramObject.value;

				if (Cube.isEmpty(value)) {
					value = '';
				}
				else if (Cube.isDate(value)) {
					value = Cube.Date.toString(value);
				}

				params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
			}

			return params.join('&');
		},

		/**
		 * Converts a query string back into an object.
		 *
		 * Non-recursive:
		 *
		 *     Cube.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
		 *     Cube.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
		 *     Cube.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
		 *     Cube.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
		 *
		 * Recursive:
		 *
		 *       Cube.Object.fromQueryString("username=Jacky&dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911&hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff", true);
		 *     // returns
		 *     {
		 *         username: 'Jacky',
		 *         dateOfBirth: {
		 *             day: '1',
		 *             month: '2',
		 *             year: '1911'
		 *         },
		 *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
		 *     }
		 *
		 * @param {String} queryString The query string to decode
		 * @param {Boolean} [recursive=false] Whether or not to recursively decode the string. This format is supported by
		 * PHP / Ruby on Rails servers and similar.
		 * @return {Object}
		 */
		fromQueryString: function(queryString, recursive) {
			var parts = queryString.replace(/^\?/, '').split('&'),
				object = {},
				temp, components, name, value, i, ln,
				part, j, subLn, matchedKeys, matchedName,
				keys, key, nextKey;

			for (i = 0, ln = parts.length; i < ln; i++) {
				part = parts[i];

				if (part.length > 0) {
					components = part.split('=');
					name = decodeURIComponent(components[0]);
					value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';

					if (!recursive) {
						if (object.hasOwnProperty(name)) {
							if (!Cube.isArray(object[name])) {
								object[name] = [object[name]];
							}

							object[name].push(value);
						}
						else {
							object[name] = value;
						}
					}
					else {
						matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
						matchedName = name.match(/^([^\[]+)/);

						//<debug error>
						if (!matchedName) {
							Cube.Error.raise({
								sourceClass: "Cube.Object",
								sourceMethod: "fromQueryString",
								queryString: queryString,
								recursive: recursive,
								msg: 'Malformed query string given, failed parsing name from "' + part + '"'
							});
						}
						//</debug>

						name = matchedName[0];
						keys = [];

						if (matchedKeys === null) {
							object[name] = value;
							continue;
						}

						for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
							key = matchedKeys[j];
							key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
							keys.push(key);
						}

						keys.unshift(name);

						temp = object;

						for (j = 0, subLn = keys.length; j < subLn; j++) {
							key = keys[j];

							if (j === subLn - 1) {
								if (Cube.isArray(temp) && key === '') {
									temp.push(value);
								}
								else {
									temp[key] = value;
								}
							}
							else {
								if (temp[key] === undefined || typeof temp[key] === 'string') {
									nextKey = keys[j+1];

									temp[key] = (Cube.isNumeric(nextKey) || nextKey === '') ? [] : {};
								}

								temp = temp[key];
							}
						}
					}
				}
			}

			return object;
		},

		/**
		 * Iterates through an object and invokes the given callback function for each iteration.
		 * The iteration can be stopped by returning `false` in the callback function. For example:
		 *
		 *     var person = {
		 *         name: 'Jacky'
		 *         hairColor: 'black'
		 *         loves: ['food', 'sleeping', 'wife']
		 *     };
		 *
		 *     Cube.Object.each(person, function(key, value, myself) {
		 *         console.log(key + ":" + value);
		 *
		 *         if (key === 'hairColor') {
		 *             return false; // stop the iteration
		 *         }
		 *     });
		 *
		 * @param {Object} object The object to iterate
		 * @param {Function} fn The callback function.
		 * @param {String} fn.key
		 * @param {Object} fn.value
		 * @param {Object} fn.object The object itself
		 * @param {Object} [scope] The execution scope (`this`) of the callback function
		 */
		each: function(object, fn, scope) {
			for (var property in object) {
				if (object.hasOwnProperty(property)) {
					if (fn.call(scope || object, property, object[property], object) === false) {
						return;
					}
				}
			}
		},

		/**
		 * Returns the first matching key corresponding to the given value.
		 * If no matching value is found, null is returned.
		 *
		 *     var person = {
		 *         name: 'Jacky',
		 *         loves: 'food'
		 *     };
		 *
		 *     alert(Cube.Object.getKey(person, 'food')); // alerts 'loves'
		 *
		 * @param {Object} object
		 * @param {Object} value The value to find
		 */
		getKey: function(object, value) {
			for (var property in object) {
				if (object.hasOwnProperty(property) && object[property] === value) {
					return property;
				}
			}

			return null;
		},

		/**
		 * Gets all values of the given object as an array.
		 *
		 *     var values = Cube.Object.getValues({
		 *         name: 'Jacky',
		 *         loves: 'food'
		 *     }); // ['Jacky', 'food']
		 *
		 * @param {Object} object
		 * @return {Array} An array of values from the object
		 */
		getValues: function(object) {
			var values = [],
				property;

			for (property in object) {
				if (object.hasOwnProperty(property)) {
					values.push(object[property]);
				}
			}

			return values;
		},

		/**
		 * Gets all keys of the given object as an array.
		 *
		 *     var values = Cube.Object.getKeys({
		 *         name: 'Jacky',
		 *         loves: 'food'
		 *     }); // ['name', 'loves']
		 *
		 * @param {Object} object
		 * @return {String[]} An array of keys from the object
		 * @method
		 */
		getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
			var keys = [],
				property;

			for (property in object) {
				if (object.hasOwnProperty(property)) {
					keys.push(property);
				}
			}

			return keys;
		},

		/**
		 * Gets the total number of this object's own properties
		 *
		 *     var size = Cube.Object.getSize({
		 *         name: 'Jacky',
		 *         loves: 'food'
		 *     }); // size equals 2
		 *
		 * @param {Object} object
		 * @return {Number} size
		 */
		getSize: function(object) {
			var size = 0,
				property;

			for (property in object) {
				if (object.hasOwnProperty(property)) {
					size++;
				}
			}

			return size;
		},

		/**
		 * 克隆对象
		 * @param {PlainObject} object 待克隆的对象
		 * @return {PlainObject} 克隆的对象
		 */
		clone: function(object) {
			var newObject = {}, name, property;
			for ( name in object ) {
				if ( object.hasOwnProperty( name ) ) {
					property = object[ name ];
					if ( typeof property !== 'object' ) {
						newObject[ name ] = property;
					} else {
						newObject[ name ] = this.clone( property );
					}
				}
			}
			return newObject;
		}
	};
})();